package org.teremail.message.builder;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.teremail.message.Header;
import org.teremail.message.MessageHeaders;
import org.teremail.message.MessageHeadersImpl;
import org.teremail.message.RFC2822;
import org.teremail.panto.IOUtil;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;

public class MessageHeadersBuilder {

    private final Set<String> TRACE_KEYS = ImmutableSet.of(
            RFC2822.RECEIVED.getName().toLowerCase(),
            RFC2822.RECEIVED_SPF.getName().toLowerCase(),
            RFC2822.DELIVERED_TO.getName().toLowerCase());

    private final ListMultimap<String,Header> returnPath = 
        LinkedListMultimap.create();
    private final ListMultimap<String,Header> receivedHeaders = 
        LinkedListMultimap.create();
    private final ListMultimap<String,Header> headers = 
        LinkedListMultimap.create();
    private boolean hasChanged;
    
    public MessageHeadersBuilder() {
        hasChanged = true;
    }
    
    public MessageHeadersBuilder(MessageHeaders original) {
        for (Header h : original) {
            putHeader(h.getName(), h);
        }
        hasChanged = false;
    }
    
    private Multimap<String,Header> getHeadersMap(String key) {
        if (RFC2822.RETURN_PATH.getName().equalsIgnoreCase(key)) {
            return returnPath;
        } else if (TRACE_KEYS.contains(key)) {
            return receivedHeaders;
        } else {
            return headers;
        }
    }
    
    private void putHeader(String key, Header header) {
        String lcKey = key.toLowerCase();
        if (RFC2822.RETURN_PATH.getName().equalsIgnoreCase(key)) {
            returnPath.put(lcKey, header);
        } else if (TRACE_KEYS.contains(lcKey)) {
            receivedHeaders.put(RFC2822.RECEIVED.getName().toLowerCase(), header);
        } else {
            headers.put(lcKey, header);
        }
    }
    
    private void replaceHeader(String key, List<Header> headers) {
        String lcKey = key.toLowerCase();
        getHeadersMap(lcKey).replaceValues(lcKey, headers);
    }

    private Collection<Header> getHeaders(String key) {
        String lcKey = key.toLowerCase();
        return getHeadersMap(lcKey).get(lcKey);
    }
    
    private Header getHeader(String key) {
        String lcKey = key.toLowerCase();
        Collection<Header> headers = getHeadersMap(lcKey).get(lcKey);
        if (headers.size() > 0) {
            return headers.iterator().next();
        } else {
            return null;
        }
    }    
    
    public MessageHeadersBuilder addReceived(String received) {
        return add(RFC2822.RECEIVED, received);
    }

    public MessageHeadersBuilder setMessageId(String messageId) {
        return set(RFC2822.MESSAGE_ID, messageId);
    }

    public MessageHeadersBuilder setReturnPath(String returnPath) {
        return set(RFC2822.RETURN_PATH, returnPath);
    }
    
    public MessageHeadersBuilder setTo(String to) {
        return set(RFC2822.TO, to);
    }
    
    public MessageHeadersBuilder setSubject(String subject) {
        return set(RFC2822.SUBJECT, subject);
    }
    
    public MessageHeadersBuilder setFrom(String from) {
        return set(RFC2822.FROM, from);
    }
    
    public MessageHeadersBuilder setSender(String sender) {
        return set(RFC2822.SENDER, sender);
    }
    
    public MessageHeadersBuilder setDate(String date) {
        return set(RFC2822.DATE, date);
    }    
    
    private MessageHeadersBuilder add(RFC2822 name, String value) {
        return add(name.getName(), value);
    }
    
    private MessageHeadersBuilder add(String name, String value) {
        hasChanged = true;
        Header header = new Header(name, value);
        putHeader(name, header);
        return this;
    }

    private MessageHeadersBuilder set(RFC2822 name, String value) {
        return set(name.getName(), value);
    }
    
    public MessageHeadersBuilder set(String name, String value) {
        hasChanged = true;
        Header header = new Header(name, value);
        replaceHeader(name, Arrays.asList(header));
        return this;
    }

    private Collection<Header> getHeaders(RFC2822 key) {
        return getHeaders(key.getName());
    }
    
    private Header getHeader(RFC2822 key) {
        return getHeader(key.getName());
    }
    
    public MessageHeaders build() {
        return new MessageHeadersImpl(getHeader(RFC2822.RETURN_PATH), 
                getHeaders(RFC2822.RECEIVED), headers);
    }

    public boolean hasChanged() {
        return hasChanged;
    }

    public void load(String messageHeaders) {
        
        String[] lines = messageHeaders.split("\r\n");
        
        StringBuilder sb = null;
        
        for (String s : lines) {
            if (s.trim().length() != 0) {
                if (s.startsWith(" ") || s.startsWith("\t")) {
                    if (sb != null) {
                        // Maintain the newline.
                        sb.append("\r\n");
                        sb.append(s);
                    } else {
                        throw new MalformedHeaderException("Malformed Header: " + s);
                    }
                } else {
                    if (sb == null) {
                        sb = new StringBuilder();
                    } else {
                        addHeader(sb);
                        sb = new StringBuilder();
                    }
                    sb.append(s);
                }
            }
        }
        
        if (sb != null) {
            addHeader(sb);
        }
    }
    
    
    public MessageHeadersBuilder add(String s) {
        addHeader(new StringBuilder(s));
        return this;
    }
    
    private void addHeader(StringBuilder sb) {
        int idx = sb.indexOf(":");
        if (idx > -1) {
            String name = sb.substring(0, idx);
            String value = sb.substring(idx + 1).trim();
            add(name, value);
        } else {
            throw new MalformedHeaderException("Malformed Header: " + sb);
        }
    }
    public int load(InputStream in) throws IOException {
        return load(in, "US-ASCII");
    }

    /**
     * Loads the message headers from an input stream.  Converting using the
     * specified charset.  It will skip any empty lines first, then will stop
     * when it receives an empty line that occurs after some text.
     * 
     * @param in
     * @param charset
     * @return
     * @throws IOException
     */
    public int load(InputStream in, String charset) throws IOException {
        int numRead = 0;
        
        StringBuilder line = new StringBuilder();
        StringBuilder header = null;
        
        // Skip any empty lines.
        // Return if we only received empty lines followed by EOF.
        while (line.length() == 0) {
            int bytesRead = IOUtil.appendLine(line, in, charset);
            if (bytesRead == 0) {
                return numRead;
            }
            numRead += bytesRead;
        }
        
        do {
            String s = line.toString();
            if (s.startsWith(" ") || s.startsWith("\t")) {
                if (header == null) {
                    throw new MalformedHeaderException("Malformed Header: " + s);
                }
                // Maintain the newline.
                header.append("\r\n");
                header.append(s);
            } else {
                if (header != null) {
                    addHeader(header);
                }
                header = new StringBuilder();
                header.append(s);
            }
            
            line.delete(0, line.length());
            numRead += IOUtil.appendLine(line, in, charset);
        } while (line.length() > 0);
        
        addHeader(header);
        
        return numRead;
    }

    public static MessageHeadersBuilder create() {
        return new MessageHeadersBuilder();
    }



}
